<html>
    <head>
        <title>本地事件总线</title>
    </head>
    <body>
        <script>
            // 本地事件总线   Local Event Bus 


                    //  事件，是服务于服务之间进行通信的手段。
                    /*
                        本地事件总线允许服务"发布" 和 "订阅"  "进程内事件"。

                        这意味着如果两个服务 (发布者 和 订阅者 ) 在同一个进程中运行，它是合适的。
                    */

                    /*
                        一、发布本地事件

                                有两种方式发布本地事件：

                                    1、使用  ILocalEventBus 服务发布事件：

                                                eg:  当产品的库存计数发生变化时，发布本地事件。

                                                        using System;
                                                        using System.Threading.Tasks;
                                                        using Volo.Abp.DependencyInjection;
                                                        using Volo.Abp.EventBus.Local;

                                                        namespace AbpDemo
                                                        {
                                                            public class MyService : ITransientDependency
                                                            {
                                                                private readonly ILocalEventBus _localEventBus;

                                                                public MyService(ILocalEventBus localEventBus)
                                                                {
                                                                    _localEventBus = localEventBus; // 注入本地事件总线服务
                                                                }
                                                                
                                                                public virtual async Task ChangeStockCountAsync(Guid productId, int newCount)
                                                                {
                                                                    // 实现你的逻辑
                                                                    
                                                                    // 发布事件
                                                                    await _localEventBus.PublishAsync(
                                                                        new StockCountChangedEvent  // 事件类型
                                                                        {
                                                                            ProductId = productId,
                                                                            NewCount = newCount
                                                                        }
                                                                    );
                                                                }
                                                            }
                                                        }  

                                                PublishAsync 方法获取一个参数： 事件对象，它负责保存与事件相关的数据。 它是一个简单的普通类：

                                                        using System;

                                                        namespace AbpDemo
                                                        {
                                                            public class StockCountChangedEvent
                                                            {
                                                                public Guid ProductId { get; set; }
                                                                
                                                                public int NewCount { get; set; }
                                                            }
                                                        }

                                                即使你不需要传递任何事件数据，你也需要创建一个事件类。 （这种情况下是一个空类）


                                   2、在 "实体" 或 "聚合根"类中发布事件

                                                实体不能通过依赖注入服务，但是在实体/聚合根类中发布本地事件是很常见的。

                                                    eg: 在聚合根方法中发布本地事件：
                                                        using System;
                                                        using Volo.Abp.Domain.Entities;

                                                        namespace AbpDemo
                                                        {
                                                            public class Product : AggregateRoot<Guid>
                                                            {
                                                                public string Name { get; set; }
                                                                
                                                                public int StockCount { get; private set; }

                                                                private Product() { }

                                                                public Product(Guid id, string name)
                                                                    : base(id)
                                                                {
                                                                    Name = name;
                                                                }

                                                                public void ChangeStockCount(int newCount)
                                                                {
                                                                    StockCount = newCount;
                                                                    
                                                                    // 发布本事件
                                                                    AddLocalEvent(
                                                                        new StockCountChangedEvent // 事件对象
                                                                        {
                                                                            ProductId = Id,
                                                                            NewCount = newCount
                                                                        }
                                                                    );
                                                                }
                                                            }
                                                        }
                                                AggregateRoot类定义AddLocalEvent以添加新的本地事件，该事件在聚合根对象保存（创建、更新或删除）到数据库时发布。
                                                
                                        (提示：如果实体发布了这样的事件，最好以受控方式更改相关属性，就像上面的示例，--StockCount只能通过发布事件的 ChangeStockCount方法进行修改)




                                        ★、IGeneratesDomainEvents 接口 

                                                实际上，添加本地事件方法 AddLocalEvent 不是 AggregateRoot 类独有的。你可以为任何实体类实现 IGeneratesDomainEvents 接口。

                                                    但是。 AggregateRoot 类默认实现它，让你轻松。

                                                    不建议为非聚合根的实体实现此接口，因为它可能不适用此类实体的某些数据库提供程序。 它适用于EF Core，单不适用用 MongoDB。！！



                                        ★、它是如何实施的？

                                                调用 AddLocalEvent 不会立即发布事件。当你将更改保存到数据库时，事件才会发布：

                                                        ①、对于EF Core，它发布在DbContext.SaveChanges上。

                                                        ②、对于MongoDB，当你调用存储库的InsertAsync、UpdateAsync或DeleteAsync方法时会发布它（因为MongoDB没有更改跟踪系统）。
                    */


                    /*
                        三、订阅本地事件 Subscribing to Events

                            1、事件处理程序 ILocalEeventHander<TEvent> 

                                    服务可以实现 ILocalEeventHander<TEvent> 服务来订阅和处理本地事件。

                                        eg：  处理上面定义的 StockCountChangeEvent 事件：

                                                    using System.Threading.Tasks;
                                                    using Volo.Abp.DependencyInjection;
                                                    using Volo.Abp.EventBus;

                                                    namespace AbpDemo
                                                    {
                                                        public class MyHandler
                                                            : ILocalEventHandler<StockCountChangedEvent>,
                                                            ITransientDependency
                                                        {
                                                            // 约定用此方法名称的方法处理事件
                                                            public async Task HandleEventAsync(StockCountChangedEvent eventData)
                                                            {
                                                                //TODO: your code that does something on the event
                                                            }
                                                        }
                                                    }

                                    就是这么简单。  MyHandler 由 ABP框架自动发现，并且每当发生 StockCountChangedEvent时，都会调用 HandleEventAsync 方法。

                                    你可以注入任何服务，并在处理程序类中执行任何所需的逻辑。

                                                    1、一个或多个处理程序可以订阅同一个事件。

                                                    2、通过为每个事件类型实现 ILocalEventHandler<TEvent> 接口，单个事件处理程序可以订阅处理多个事件。

                                    如果你执行数据库操作并使用事件处理程序内的存储库，你可能需要创建一个工作单元，因为某些存储库方法需要在工作单元内工作。

                                    把事件处理方法定义为 virtual 并为该方法添加一个 [UnitOfWork]特性，或者手动使用 IUnitOfWorkManager接口创建一个工作单元。


                                    
                                    ★、处理程序类必须注册到依赖注入（DI）。上面的示例使用ITransientDependency来完成它。有关更多选项，请参阅DI文档。


                            2、[LocalEventHandlerOrder] 特性    本地事件处理执行顺序
                            
                                    [LocalEventHandlerOrder]特性可用于设置事件处理程序的执行顺序，如果你想以特定的顺序执行事件处理程序，这会很有帮助：

                                                    [LocalEventHandlerOrder(-1)] // 执行顺序
                                                    public class MyHandler
                                                        : ILocalEventHandler<StockCountChangedEvent>,
                                                        ITransientDependency
                                                    {
                                                        public async Task HandleEventAsync(StockCountChangedEvent eventData)
                                                        {
                                                            //TODO: 事件处理逻辑
                                                        }
                                                    }      
                                    默认情况下，所有事件处理程序的顺序值为0.因此，如果你想在其他事件处理程序之前执行某个事件处理程序，你可以将顺序值设置为负值。



                            3、事务 和 异常行为   Transaction & Exception Behavior

                                    事件处理程序总是在"相同的工作单元作用域"中执行，这意味着在"与发布事件的代码相同数据库事务中"。

                                    如果事件处理程序抛出异常，则工作单元(数据库事务)将回滚。

                                    因此，如果你想要隐藏异常，请在事件处理程序中使用 try/catch块。

                                    
                                    
                                    
                                    当你调用 ILocalEventBus.PublishAsync时， 事件处理程序不会立即执行。

                                    相反，它们会在当前工作单元完成之前执行 (处理程序中未处理的异常仍会回滚当前工作单元)。

                                    如果要立即执行处理程序，请将onUnitOfWorkComplete 参数设置为 false.

                                    (在实体/聚合根类中发布事件时，onUnitOfWorkComplete选项不可用（请参阅在实体/聚合根类中发布事件部分））
                     */

                     /*
                        四、预构建本地事件  Pre-built Events


                              1、订阅处理预构建事件：

                                            在实体创建、更新 和 删除等操作上发布事件是很常见的。

                                            ABP自动为所有实体发布这些事件。你可以直接订阅这些事件。

                                                    eg: 订阅创建用户时发布的事件

                                                        using System.Threading.Tasks;
                                                        using Microsoft.AspNetCore.Identity;
                                                        using Volo.Abp.DependencyInjection;
                                                        using Volo.Abp.Domain.Entities.Events;
                                                        using Volo.Abp.EventBus;

                                                        namespace AbpDemo
                                                        {
                                                            public class MyHandler
                                                                : ILocalEventHandler<EntityCreatedEventData<IdentityUser>>,  // 订阅创建实体事件 (abp内置事件类型)
                                                                ITransientDependency
                                                            {
                                                                public async Task HandleEventAsync(
                                                                    EntityCreatedEventData<IdentityUser> eventData)
                                                                {
                                                                    var userName = eventData.Entity.UserName;
                                                                    var email = eventData.Entity.Email;
                                                                }
                                                            }
                                                        }

                                            此类订阅 EntityCreatedEventData<IdentityUser>，它在用户创建后(但是，在当前事务完成之前)发布。


                            2、ABP预构建事件类型列表：

                                        ①、EntityCreatedEventData<TEntity>:  在成功创建实体后发布。

                                        ②、EntityUpdatedEventData<TEntity>:  在实体成功更新后发布。

                                        ③、EntityDeletedEventData<TEntity>:  在成功删除实体后发布。

                                        ④、EntityChangedEventData<TEntity>:  在成功创建、更新、删除实体后发布。  
                                                                             (如果你想监听实体的任何更改，它可以是一种快捷方式，而不是订阅单个的事件)
                                


                            3、工作原理

                                        将更改保存到数据库时会发布预构建事件：

                                            对于EF Core来说，它们发布在 DbContext.SaveChanges上。

                                            对于MongoDB，当你调用存储库的InsertAsync、UpdateAsync或DeleteAsync方法时，它们会被发布（因为MongoDB没有更改跟踪系统）。


                            4、AbpEntityChangeOptions 选项类：

                                        AbpEntityChangeOptions 类中有一个 PublisEntityUpdatedEventWhenNavigationChanges 选项，默认值为 true.

                                        如果将其设置为 false,则当导航属性更改时，将不会发布 EntityUpdatedEventData<TEntity>事件。
                                        
                                        (此选项仅用于 EF Core，因为MongoDB没有更改跟踪系统。)
                                
                     */
        </script>
    </body>
</html>